#include "record_set.h"
#include "exceptions.h"
#include "macros.h"
#include "str.h"
#include "global_variables.h"
#include "array1d.h"

ostream &operator<<(ostream &os, const record_set &rs) {
  const unsigned int columns = rs.get_column_count();
  const unsigned int rows = rs.get_row_count();

  array1d<unsigned int> column_max_width(columns); // maximum length of each column, required for formated output
  const char *data = 0;
  size_t total = 0;

  for (unsigned int j = 0; j < columns; ++j)
    column_max_width(j) = rs.get_column_length(j);

  for (unsigned int i = 0; i < rows; ++i) {
    for (unsigned int j = 0; j < columns; ++j)
      column_max_width(j) = max(column_max_width(j), rs.get_length(i, j));

  } // for i

  for (unsigned int j = 0; j < columns; ++j) {
    data = rs.get_column_name(j);
    if (data != 0)
      os << setw(column_max_width(j)) << left << data << "  "; // column names
    else
      os << setw(column_max_width(j) + strlen("  ")) << global_empty_string;

    total += column_max_width(j) + strlen("  ");
  } // for j

  os << endl;
  os << "+";
  os << setw(total - 1) << setfill('-') << right << "+";
  os << endl;
  os << setfill(' '); // reset

  for (unsigned int i = 0; i < rows; ++i) {
    for (unsigned int j = 0; j < columns; ++j) {
      data = rs(i, j);
      if (data != 0)
        os << setw(column_max_width(j)) << left << data << "  ";
      else
        os << setw(column_max_width(j) + strlen("  ")) << global_empty_string; // this makes sure that null values are printed correctly
    } // for j

    os << endl;
  } // for i

  return os;
} // operator<<

void record_set::fill_record_set(const char *value, unsigned int length) {
  char *tmp = cim.get_new_instance(length); // null termination(s) will be managed by instance_manager
  memcpy(tmp, value, length); // we do not use strcpy here because of binary values, which may contain '\0'

  data << tmp;
  data_length << length;
} // record_set::fill_record_set

void record_set::fill_record_set(unsigned int row, unsigned int column, const char *value, unsigned int length) {
  char *tmp = 0;
  #pragma omp critical
  {
    tmp = cim.get_new_instance(length); // null termination(s) will be managed by instance_manager
  }

  memcpy(tmp, value, length); // we do not use strcpy here because of binary values, which may contain '\0'

  data(row, column) = tmp;
  data_length(row, column) = length;
}

void record_set::reserve(unsigned int rows, unsigned int columns) {
  data.resize(rows + 1, columns, 0);
  data_length.resize(rows + 1, columns, 0);
} // record_set::reserve

void record_set::reconstruct(unsigned int total_rows, unsigned int total_columns) {
  data.reconstruct(total_rows + 1, total_columns);
  data_length.reconstruct(total_rows + 1, total_columns);

  cim.release(); // should be released to reduce memory consumption of the record_set (when the object is reused)
} // record_set::reconstruct

void record_set::cut_rows_after(unsigned int index) {
  data.cut_rows_after(index + 1);
  data_length.cut_rows_after(index + 1);
} // record_set::cut_rows_after

unsigned int record_set::get_row_count() const {
  const unsigned int total_rows = data.get_row_count();
  if (total_rows > 0)
    return total_rows - 1;
  else
    return 0;
} // record_set::get_row_count

unsigned int record_set::get_column_count() const {
  return data.get_column_count();
} // record_set::get_column_count

const char *record_set::get_column_name(unsigned int index) const {
  return data(0, index); // indexes will be checked by array2d itself
} // record_set::get_column_name

unsigned int record_set::get_column_index(const char *column_name) const {
  const unsigned int column_index = data.get_column_count();

  for (unsigned int i = 0; i < column_index; ++i) {
    if (strcmp(data(0, i), column_name) == 0)
      return i; // indexes will be checked by array2d itself

  } // for i

  return UINT_MAX;
}

unsigned int record_set::get_column_length(unsigned int index) const {
  return data_length(0, index);
} // record_set::get_column_length

unsigned int record_set::get_length(unsigned int row, unsigned int column) const {
  return data_length(row + 1, column);
} // record_set::get_length

const char *record_set::operator()(unsigned int row, unsigned int column) const {
  return data(row + 1, column); // indexes will be checked by array2d itself
} // record_set::operator()

const char *record_set::operator()(unsigned int row, const char *column_name) const {
  const unsigned int i = get_column_index(column_name);
  if (i != UINT_MAX)
    return data(row + 1, i); // indexes will be checked by array2d itself

  string msg = "Column name '%1' not found!";
  str::format(msg, arg<const char*>(column_name));
  throw index_exception(msg);
} // record_set::operator()

bool record_set::compare(const record_set &rs, array2d<bool> &comparison_array, bool check_column_names) const {
  comparison_array.clear();

  const unsigned int rows = this->get_row_count();
  const unsigned int columns = this->get_column_count();

  if (columns != rs.get_column_count() || rows != rs.get_row_count()) {
    string msg = "Number of rows & columns are not the same: array(%1, %2) != array(%3, %4)!";
    str::format(msg, arg<unsigned int>(rows, columns, rs.get_row_count(), rs.get_column_count()));
    throw parameter_exception(msg);
  } // if

  if (check_column_names == true) {
    for (unsigned int j = 0; j < columns; ++j) {
      if (strcmp(this->get_column_name(j), rs.get_column_name(j)) != 0) {
        string msg = "Column names must be equal: %1 != %2!";
        str::format(msg, arg<const char*>(this->get_column_name(j), rs.get_column_name(j)));
        throw parameter_exception(msg);
      } // if
    } // for j
  } // if

  bool arrays_are_equal = true;
  comparison_array.resize(rows, columns, false);

  #pragma omp parallel for if ((rows * columns) > MINIMUM_SIZE_LIMIT_OMP) collapse(2)
  for (unsigned int i = 0; i < rows; ++i) {
    for (unsigned int j = 0; j < columns; ++j) {
      if (strncmp((*this)(i, j), rs(i, j), this->get_length(i, j)) != 0) {
        comparison_array(i, j) = true; // mark the changed field in the array
        arrays_are_equal = false;
      } // if
    } // for j
  } // for i

  if (arrays_are_equal == true)
    comparison_array.clear();

  return arrays_are_equal;
} // record_set::compare

record_set::record_set() : cim(CHAR_INSTANCE_MANAGER_SIZE) {
  // nothing to do
} // record_set::record_set

record_set::~record_set() {
  // nothing to do
} // record_set::~record_set
